//
//  Networking.m
//
//
//  Created by mysj on 16/7/11.
//  Copyright © 2016年 zeng. All rights reserved.
//

#import "Networking.h"
#import <CommonCrypto/CommonDigest.h>
#import <AFNetworking.h>
#import "AFNetworkActivityIndicatorManager.h"


static AFHTTPSessionManager *   _sharedManager = nil;

static Networking *             z_sharedNetworking = nil;
static NetworkStatus            z_netorkStatus = kNetworkStatusUnknown;
static NSString *               z_baseUrl = nil;
static NSTimeInterval           z_timeout = 30.0f;
static NSUInteger               z_maxCacheSize = 10 *1024 * 1024;
static NSMutableArray *         z_requestTasks;
static NSDictionary *           z_httpHeaders = nil;





@interface NSString (ex)

- (NSString *)md5;
@end

@implementation NSString (ex)

- (NSString *)md5 {
    if (self == nil || self.length == 0) {
        return nil;
    }
    
    unsigned char digest[CC_MD5_DIGEST_LENGTH], i;
    CC_MD5([self UTF8String], (int)[self lengthOfBytesUsingEncoding:NSUTF8StringEncoding], digest);
    NSMutableString *ms = [NSMutableString string];
    
    for (i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
        [ms appendFormat:@"%02x", (int)(digest[i])];
    }
    return [ms copy];
}

@end

@protocol NetworkingProxy <NSObject>

/// AFN 内部网络请求方法
///
/// @param method     请求方法
/// @param URLString  URLString
/// @param parameters 参数字典
/// @param uploadProgress 上传进度
/// @param downloadProgress 下载进度
/// @param success    成功回调
/// @param failure    失败回调
///
/// @return NSURLSessionDataTask，需要 resume
@optional
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure;
@end

@interface Networking () <NetworkingProxy>


@end

@implementation Networking

+ (void)load {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if ([self totalCacheSize] > z_maxCacheSize) {
            [self clearCaches];
        }
    });
}



#pragma mark -
+ (void)get:(NSString *)url parameters:(NSDictionary *)parameters Response:(Response)response {
    
    [self _request:GET URLString:url parameters:parameters uploadProgress:nil downloadProgress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
        response(responseObject, nil);
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        response(nil, error);
    }];
}

+ (void)post:(NSString *)url parameters:(NSDictionary *)parameters Response:(Response)response {
    
    [self _request:POST URLString:url parameters:parameters uploadProgress:nil downloadProgress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
        
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        
    }];
}

+ (NSURLSessionDataTask *)_request:(HTTPMethod)method
                         URLString:(NSString *)URLString
                        parameters:(id)parameters
                    uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                  downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                           success:(void (^)(NSURLSessionDataTask *task, id responseObject)) success
                           failure:(void (^)(NSURLSessionDataTask *task, NSError * error)) failure {
    
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    NSString *metohStr = method == GET? @"GET" : @"POST";
    NSURLSessionDataTask *task = [[self _manager] dataTaskWithHTTPMethod:metohStr URLString:URLString parameters:parameters uploadProgress:uploadProgress downloadProgress:downloadProgress success:^(NSURLSessionDataTask *task, id responseObject) {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        success(task, responseObject);
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        failure(task, error);
    }];
    [task resume];
    
    return task;
}

+ (Networking *)_manager {
    @synchronized (self) {
        
        if (z_baseUrl) {
            z_sharedNetworking = [[self alloc] initWithBaseURL:[NSURL URLWithString:z_baseUrl]];
        } else {
            z_sharedNetworking = [[self alloc] initWithBaseURL: nil];
        }
        
        //        if (_requestType == kRequestTypePlainText)
        //        {
        //            z_sharedNetworking.requestSerializer = [AFHTTPRequestSerializer serializer];
        //        }
        
        //        z_sharedNetworking.responseSerializer = [AFJSONResponseSerializer serializer];
        
        //        if (_responseType == kResponseTypeData) {
        //            z_sharedNetworking.responseSerializer = [AFXMLParserResponseSerializer serializer];
        //        } else if (_responseType == kResponseTypeData) {
        //            z_sharedNetworking.responseSerializer = [AFHTTPResponseSerializer serializer];
        //        }
        
        //        配置公共的请求头
        //        for (NSString *key in _httpHeaders.allKeys) {
        //            if (_httpHeaders[key] != nil) {
        //                [z_sharedNetworking.requestSerializer setValue:_httpHeaders[key] forHTTPHeaderField:key];
        //            }
        //        }
        
        z_sharedNetworking.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json",
                                                                        @"text/html",
                                                                        @"text/json",
                                                                        @"text/plain",
                                                                        @"text/javascript",
                                                                        @"text/xml",
                                                                        @"image/*", nil];
        
        z_sharedNetworking.requestSerializer.timeoutInterval = z_timeout;
        
         z_sharedNetworking.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
        
        AFJSONResponseSerializer *respose = [AFJSONResponseSerializer serializer];
        
        z_sharedNetworking.responseSerializer = respose;
        
        respose.removesKeysWithNullValues = YES;
        
        z_sharedNetworking.operationQueue.maxConcurrentOperationCount = 3;
        
        [self detectNetwork];
        
        return  z_sharedNetworking;
    }
}


+ (instancetype)sharedNetworking {
    if (z_sharedNetworking == nil) {
        z_sharedNetworking = [self manager];
    }
    return z_sharedNetworking;
}

static inline NSString *cachePath() {
    return [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/NetworkingCaches"];
}

+ (NSMutableArray *)allTasks {
    
    if (z_requestTasks == nil) {
        z_requestTasks = [NSMutableArray arrayWithCapacity:10];
    }
    return z_requestTasks;
}



+ (unsigned long long)totalCacheSize {
    NSString *directoryPath = cachePath();
    BOOL isDir = NO;
    unsigned long long total = 0;
    
    if ([[NSFileManager defaultManager] fileExistsAtPath:directoryPath isDirectory:&isDir]) {
        if (isDir) {
            NSError *error = nil;
            NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:&error];
            
            if (error == nil) {
                for (NSString *subpath in array) {
                    NSString *path = [directoryPath stringByAppendingPathComponent:subpath];
                    NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:path
                                                                                          error:&error];
                    if (!error) {
                        total += [dict[NSFileSize] unsignedIntegerValue];
                    }
                }
            }
        }
    }
    
    return total;
}

+ (void)clearCaches {
    
    NSString *directoryPath = cachePath();
    if ([[NSFileManager defaultManager] fileExistsAtPath:directoryPath isDirectory:nil]) {
        NSError *error = nil;
        [[NSFileManager defaultManager] removeItemAtPath:directoryPath error:&error];
        
        if (error) {
            Log(@"Networking clear caches error: %@", error);
            return;
        }
        Log(@"Networking clear caches ok");
    }
}

+ (void)cancelAllRequest {
    @synchronized(self) {
        [[self allTasks] enumerateObjectsUsingBlock:^(NSURLSessionDataTask * _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) {
            if ([task isKindOfClass:[NSURLSessionDataTask class]]) {
                [task cancel];
            }
        }];
        
        [[self allTasks] removeAllObjects];
    };
    
}

+ (void)cancelRequestWithURL:( NSString *)url {
    @synchronized (self) {
        
        [[self  allTasks] enumerateObjectsUsingBlock:^(NSURLSessionDataTask *  _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) {
            if ([task isKindOfClass:[NSURLSessionDataTask class]] && [task.currentRequest.URL.absoluteString hasSuffix:url]) {
                [task cancel];
                [[self allTasks] removeObject:task];
                return;
            }
        }];
    }
}


/*===============================================================================================================================*/
/*===============================================================================================================================*/
/*===============================================================================================================================*/



+ (void)setBaseUrl:(NSString *)baseUrl Timeout:(NSTimeInterval)timeout HttpHeaders:(NSDictionary *)httpHeaders {
    
    z_baseUrl = baseUrl;
    z_timeout = timeout;
    
}

+ (NSURLSessionDataTask *)uploadWithImage:(UIImage *)image url:(NSString *)url filename:(NSString *)filename name:(NSString *)name mimeType:(NSString *)mimeType parameters:(NSDictionary *)parameters progress:(Progress)progress Response:(Response)response {
    
    AFHTTPSessionManager *manger = [self manager];
    
    NSURLSessionDataTask *session = [manger POST:url parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
        NSData *imageDate = UIImageJPEGRepresentation(image, 1);
        
        NSString *imageFileName = filename;
        if (filename.length == 0) {
            NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
            formatter.dateFormat = @"yyyyMMddHHmmss";
            NSString *str = [formatter stringFromDate:[NSDate date]];
            imageFileName = [NSString stringWithFormat:@"%@.jpg", str];
        }
        
        [formData appendPartWithFileData:imageDate name:name fileName:filename mimeType:mimeType];
    } progress:^(NSProgress * _Nonnull uploadProgress) {
        if (progress) {
            progress(uploadProgress.completedUnitCount, uploadProgress.totalUnitCount);
        }
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        [[self allTasks] removeObject:task];
        
        response(responseObject, nil);
        
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        [[self allTasks] removeObject:task];
        
        response(nil, error);
    }];
    
    [session resume];
    if (session) {
        [[self allTasks] addObject:session];
    }
    
    return session;
}

+ (NSURLSessionDataTask *)uploadFileWithUrl:(NSString *)url uploadingFile:(NSString *)uploadingFile progress:(Progress)progress Response:(Response)response {
    
    NSURL *uploadURL = nil;
    if (z_baseUrl == nil) {
        uploadURL = [NSURL URLWithString:url];
    } else {
        uploadURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", z_baseUrl, url]];
    }
    
    if (uploadURL == nil) {
        Log(@"url错误 %@",url);
        return nil;
    }
    
    AFHTTPSessionManager *manager = [self _manager];
    NSURLRequest *request = [NSURLRequest requestWithURL:uploadURL];
    NSURLSessionDataTask *session = nil;
    
    [manager uploadTaskWithRequest:request fromFile:[NSURL URLWithString:uploadingFile] progress:^(NSProgress * _Nonnull uploadProgress) {
        if (progress) {
            progress(uploadProgress.completedUnitCount, uploadProgress.totalUnitCount);
        }
    } completionHandler:^(NSURLResponse * _Nonnull zresponse, id  _Nullable responseObject, NSError * _Nullable error) {
        [[self allTasks] removeObject:session];
        
        if (responseObject) {
            response(responseObject, nil);
        }
        
        if ([error code] == NSURLErrorCancelled) {
            response(nil, error);
        }
    }];
    
    if (session) {
        [[self allTasks] addObject:session];
    }
    
    return session;
}

+ (NSURLSessionDownloadTask *)download:(NSString *)url saveToPath:(NSString *)saveToPath progress:(Progress)progress Response:(Response)response {
    
    
    NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
    AFHTTPSessionManager *manager = [self _manager];
    
    NSURLSessionDownloadTask *session = nil;
    
    session = [manager downloadTaskWithRequest:downloadRequest progress:^(NSProgress * _Nonnull downloadProgress) {
        if (progress) {
            progress(downloadProgress.completedUnitCount, downloadProgress.totalUnitCount);
        }
    } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        return [NSURL fileURLWithPath:saveToPath];
    } completionHandler:^(NSURLResponse * _Nonnull zresponse, NSURL * _Nullable filePath, NSError * _Nullable error) {
        [[self allTasks] removeObject:session];
        
        if (error == nil) {
            
            response(filePath.absoluteString, nil);
        } else {
            
            if ([error code] == NSURLErrorCancelled) {
                response(nil, error);
            }
        }
    }];
    
    [session resume];
    if (session) {
        [[self allTasks] addObject:session];
    }
    
    return session;
}



#pragma mark -
+ (void)detectNetwork {
    AFNetworkReachabilityManager *reachabilityManager = [AFNetworkReachabilityManager sharedManager];
    
    [reachabilityManager startMonitoring];
    [reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        if (status == AFNetworkReachabilityStatusNotReachable){
            z_netorkStatus = kNetworkStatusNotReachable;
        } else if (status == AFNetworkReachabilityStatusUnknown){
            z_netorkStatus = kNetworkStatusUnknown;
        } else if (status == AFNetworkReachabilityStatusReachableViaWWAN){
            z_netorkStatus = kNetworkStatusReachableViaWWAN;
        } else if (status == AFNetworkReachabilityStatusReachableViaWiFi){
            z_netorkStatus = kNetworkStatusReachableViaWiFi;
        }
    }];
}


+ (NSString *)absoluteUrlWithPath:(NSString *)path {
    if (path == nil || path.length == 0) {
        return @"";
    }
    
    if (z_baseUrl == nil || [z_baseUrl length] == 0) {
        return path;
    }
    
    NSString *absoluteUrl = path;
    
    if (![path hasPrefix:@"http://"] && ![path hasPrefix:@"https://"]) {
        if ([z_baseUrl hasSuffix:@"/"]) {
            
            if ([path hasPrefix:@"/"]) {
                NSMutableString * mutablePath = [NSMutableString stringWithString:path];
                [mutablePath deleteCharactersInRange:NSMakeRange(0, 1)];
                absoluteUrl = [NSString stringWithFormat:@"%@%@",z_baseUrl, mutablePath];
            } else {
                absoluteUrl = [NSString stringWithFormat:@"%@%@",z_baseUrl, path];
            }
            
        } else {
            if ([path hasPrefix:@"/"]) {
                absoluteUrl = [NSString stringWithFormat:@"%@%@",z_baseUrl, path];
            } else {
                absoluteUrl = [NSString stringWithFormat:@"%@/%@",z_baseUrl, path];
            }
        }
    }
    
    return absoluteUrl;
}

#pragma mark - chace

+ (id)cahceResponseWithURL:(NSString *)url parameters:params {
    id cacheData = nil;
    
    if (url) {
        
        NSString *directoryPath = cachePath();
        NSString *absoluteURL = [self generateGETAbsoluteURL:url params:params];
        NSString *key = absoluteURL.md5;
        NSString *path = [directoryPath stringByAppendingPathComponent:key];
        
        NSData *data = [[NSFileManager defaultManager] contentsAtPath:path];
        if (data) {
            cacheData = data;
            Log(@"Read data from cache for url: %@\n", url);
        }
    }
    
    return cacheData;
}

+ (void)cacheResponseObject:(id)responseObject request:(NSURLRequest *)request parameters:params {
    if (request && responseObject && ![responseObject isKindOfClass:[NSNull class]]) {
        NSString *directoryPath = cachePath();
        
        NSError *error = nil;
        
        if (![[NSFileManager defaultManager] fileExistsAtPath:directoryPath isDirectory:nil]) {
            [[NSFileManager defaultManager] createDirectoryAtPath:directoryPath
                                      withIntermediateDirectories:YES
                                                       attributes:nil
                                                            error:&error];
            if (error) {
                Log(@"create cache dir error: %@\n", error);
                return;
            }
        }
        
        NSString *absoluteURL = [self generateGETAbsoluteURL:request.URL.absoluteString params:params];
        NSString *key = absoluteURL.md5;
        NSString *path = [directoryPath stringByAppendingPathComponent:key];
        NSDictionary *dict = (NSDictionary *)responseObject;
        
        NSData *data = nil;
        if ([dict isKindOfClass:[NSData class]]) {
            data = responseObject;
        } else {
            data = [NSJSONSerialization dataWithJSONObject:dict
                                                   options:NSJSONWritingPrettyPrinted
                                                     error:&error];
        }
        
        if (data && error == nil) {
            BOOL isOk = [[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil];
            if (isOk) {
                Log(@"cache file ok for request: %@\n", absoluteURL);
            } else {
                Log(@"cache file error for request: %@\n", absoluteURL);
            }
        }
    }
}

// 仅对一级字典结构起作用
+ (NSString *)generateGETAbsoluteURL:(NSString *)url params:(id)params {
    if (params == nil || ![params isKindOfClass:[NSDictionary class]] || [params count] == 0) {
        return url;
    }
    
    NSString *queries = @"";
    for (NSString *key in params) {
        id value = [params objectForKey:key];
        
        if ([value isKindOfClass:[NSDictionary class]]) {
            continue;
        } else if ([value isKindOfClass:[NSArray class]]) {
            continue;
        } else if ([value isKindOfClass:[NSSet class]]) {
            continue;
        } else {
            queries = [NSString stringWithFormat:@"%@%@=%@&", (queries.length == 0 ? @"&" : queries), key, value];
        }
    }
    
    if (queries.length > 1) {
        queries = [queries substringToIndex:queries.length - 1];
    }
    
    if (([url hasPrefix:@"http://"] || [url hasPrefix:@"https://"]) && queries.length > 1) {
        if ([url rangeOfString:@"?"].location != NSNotFound
            || [url rangeOfString:@"#"].location != NSNotFound) {
            url = [NSString stringWithFormat:@"%@%@", url, queries];
        } else {
            queries = [queries substringFromIndex:1];
            url = [NSString stringWithFormat:@"%@?%@", url, queries];
        }
    }
    
    return url.length == 0 ? queries : url;
}

@end
